/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package gfs;

import java.util.ArrayList;
import java.awt.Color;
import gfs.StrategoSoldier.Type;
import java.awt.event.MouseEvent;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author Mark Vismonte
 * @author Joel Jauregui
 * @version NetStrat2.1
 */

public class Board extends Object
    {
    public Board(boolean isHost)
        {
        super();
        rows = makeRowsSubCol(
                Coordinate.getArrayList(X_MIN, X_MAX, Y_MIN, Y_MAX, MAX_SIDES));
        local = new StrategoPlayer(IS_LOCAL_PLAYER, this, isHost);
        netBuddy = new StrategoPlayer(IS_NETWORK_PLAYER, this, isHost);
        lib = new StrategoGraphicLib();
        lakes = StrategoLake.lakeCoors(this);
        //Lib must be before lakes so lakes can use the draw? maybe?
        soldiers = new ArrayList<StrategoSoldier>(netBuddy.getSoldiers()
                .size() + local.getSoldiers().size());
        for (StrategoSoldier sold : local.getSoldiers())
            soldiers.add(sold);
        for (StrategoSoldier sold : netBuddy.getSoldiers())
            soldiers.add(sold);
        map = new Object[10][10];
        addLakesToMap();
        winner = false;
        
        }
    
    /* ArrayList of 10 arraylists
     * The arraylists all of 10 coordinates which correspond to the column
     * Example call: rows.get(2).get(5)
     * This will get the element at row 2 and column 5
     */
    private ArrayList<ArrayList<Coordinate>> makeRowsSubCol(
            ArrayList<Coordinate> input)
        {
        int numRows = input.size() / 10;
        ArrayList<ArrayList<Coordinate>> row = 
                new ArrayList<ArrayList<Coordinate>>(numRows);
        for (int i = 0; i < MAX_SIDES; i++)
            {
            ArrayList<Coordinate> col = new ArrayList<Coordinate>(MAX_SIDES);
            for (int x = 0; x < MAX_SIDES; x++)
                col.add(input.remove(0));
            row.add(col);
            }
        return row;
        }

    public Object[][] getMap() 
        {
        return map;
        }
    
    private boolean addLakesToMap()
        {
        map[4][1] = lakes.get(0);
        map[4][2] = lakes.get(1);
        map[5][2] = lakes.get(2);
        map[5][1] = lakes.get(3);
        map[4][7] = lakes.get(4);
        map[4][8] = lakes.get(5);
        map[5][7] = lakes.get(6);
        map[5][8] = lakes.get(7);
        return true;
        }
            
    
    /**
     * Gets a soldier from the specified row and column
     * 
     * @param row
     *      The row the soldier is at
     * @param col
     *      The column the soldier it at
     * @return
     *      Null if there is not a soldier there or the soldier if there is
     */
    public StrategoSoldier getSoldierAt(int row, int col)
        {
        System.out.println("The row being checked is " + row + "\nThe col being" +
                " checked is col " + col);
        Object obj = map[row][col];
        if(obj != null)
            if(!(obj instanceof StrategoSoldier))
                return null;
        return (StrategoSoldier)obj;
        }
    
    /**
     * Set an object to the board map
     * 
     * @param o
     *      The object to be set to the map
     * @param row
     *      The row at which it is to be set
     * @param col
     *      The column at which it is to be set
     * @return
     *      True if the object is set correctly
     */
    public boolean setObjectToMap(Object o, int row, int col)
        {
        map[row][col] = o;
        return true;
        }
    
    @SuppressWarnings("empty-statement")
    public int[] drawBoard()
        {
        while(!lib.initLib());
        drawLakes();
        lib.setPenWidth(3);
        lib.setPenColor(Color.BLACK);
        //Take from the original Stratego board project
        for (int i = -(lib.getHeight()/2); i <= lib.getHeight()/2; i += 60)
            lib.drawLine(-lib.getHeight()/2 , i, lib.getHeight()/2, i);
        for (int i = -(lib.getWidth()/2); i <= lib.getWidth()/2; i += 60)
                lib.drawLine(i, -lib.getWidth()/2 , i, lib.getWidth()/2);
        lib.setPenWidth(1);
        if (!setPieces())
            throw new UnknownError("Set Pieces is false.");
        int[] hashing = new int[40];
        int i = 0;
        for (StrategoSoldier ss: local.getSoldiers())
            hashing[i++] = ss.getCoordinate().getMirroredCoordinate().hashCode();
        return hashing;
        }
    
    public boolean netBuddysSoldiersHaveCoordinates()
        {
        for (StrategoSoldier ss: netBuddy.getSoldiers())
            if (ss.getCoordinate() == null)
                return false;
        return true;
        }
    
    public boolean setNetBuddyPieceCoordinate(int[] xxx)
        {
        int i = 0;
        for (StrategoSoldier ss : netBuddy.getSoldiers())
            {
            Coordinate cd = Coordinate.getCoorFromHash(xxx[i++]);
            ss.setCoordinate(cd);
            }
        return true;
        }
    
    public void redrawLines()
        {
        lib.setPenWidth(3);
        lib.setPenColor(Color.BLACK);
        //Take from the original Stratego board project
        for (int i = -(lib.getHeight()/2);
        i <= lib.getHeight()/2; i += 60)
                lib.drawLine(-lib.getHeight()/2
                        , i, lib.getHeight()/2, i);
        for (int i = -(lib.getWidth()/2); 
        i <= lib.getWidth()/2; i += 60)
                lib.drawLine(i, -lib.getWidth()/2
                        , i, lib.getWidth()/2);
        lib.setPenWidth(1);
        }
    
    public StrategoGraphicLib getLib()
        {
        return lib;
        }
    
    private void drawLakes()
        {
        for (StrategoLake lake: lakes)
            lake.draw();
        }
    
    public void drawCoor(int row, int col)
        {
        Coordinate dr = getCoordinate(row, col);
        lib.drawCircle(dr.getX(), dr.getY(), FINAL_RAD);
        }
    
    public void drawCoor(Coordinate dr)
        {
        lib.drawCircle(dr.getX(), dr.getY(), FINAL_RAD);
        }
    
    public void setPenColor(Color c)
        {
        lib.setPenColor(c);
        }
    
    public Coordinate getCoordinate(int row, int col)
        {
        DebugLog.AssertAndThrow(row < MAX_SIDES || row > 0, "Out of Bounds Row " +
                "value: " + row);
        DebugLog.AssertAndThrow(col < MAX_SIDES || col > 0, "Out of Bounds Col " +
                "value: " + col);
        return rows.get(row).get(col);
        }
    
    public void setLocalPlayer(StrategoPlayer me)
        {
        if(me != null)
            local = me;
        }
    
    public void setNetworkPlayer(StrategoPlayer him)
        {
        if(him != null)
            netBuddy = him;
        }
    
    public StrategoPlayer getLocalPlayer()
        {
        return local;
        }
    
    public StrategoPlayer getNetworkPlayer()
        {
        return netBuddy;
        }
    
    public ArrayList<StrategoSoldier> getSoldiers()
        {
        return soldiers;
        }
    
    public StrategoSoldier getSoldier(Coordinate cd)
        {
        for(StrategoSoldier sold : soldiers)
            {
            if(sold.getCoordinate().equals(cd))
                return sold;
            }
        return null;
        }
    
    public void setSoldier(int row, int col, StrategoSoldier sold, boolean local)
        {
        soldiers.add(sold);
        this.local.setSoldier(sold);
        sold.draw(local);
        }
    
    public ArrayList<Coordinate> getAdjacentMoveCoordinates(Coordinate cd) throws Exception
        {
        ArrayList<Coordinate> coors = new ArrayList<Coordinate>();
        int initRow = cd.getRow();
        int initCol = cd.getCol();
        for(int x = 0, row = initRow - 1, col = initCol - 1; x < 9; x++)
            {
            Coordinate coor = getCoordinate(row++, col++);
            if(!(cd.equals(coor)) && (x % 2 != 0) && !local.isMine(
                    getSoldierAt(coor)))
                {
                coors.add(coor);
                System.out.println("Adding: " + coor);
                }
            } 
        return coors;
        }
    
    /**
     * Causes a turn to execute copying the specificed event to this board and
     * checking if the player has won or lost then prompting them to carry out 
     * their turn
     * 
     * @param ev
     *      The <code>StrategoEvent</code> to copy to this board
     * @return
     *      The <code>StrategoEvent</code> of the soldier you moved to be copied 
     * to the network player's board
     */
    public StrategoEvent doTurn(StrategoEvent ev)
        {
        System.out.println("Beginning turn");
        copyEvent(ev);
        redrawLines();
        if(checkVictory())
            {
            StrategoIO.infoMsg("YOU WON!!!", "Results");
            winner = true;
            return null;
            }
        if(checkLoss())
            {
            StrategoIO.infoMsg("YOU LOST NUB!!!", "Results");
            winner = true;
            return null;
            }
        System.out.println("here : 1");
        StrategoSoldier setting = chooseMovePieces();
        System.out.println("here : 3");
        Coordinate from = setting.getCoordinate();
        System.out.println("here : 4");
        Coordinate to = null;
        try {
            to = moveSoldier(setting);
        } catch (InterruptedException ex) {
            Logger.getLogger(Board.class.getName()).log(Level.SEVERE, null, ex);
        }
        turn = false;
        if(to != null && checkVictory())
            {
            StrategoIO.infoMsg("YOU WON!!!", "Results");
            winner = true;
            return new StrategoEvent(from.getMirroredCoordinate().hashCode(), 
                to.getMirroredCoordinate().hashCode());
            }
        if(checkLoss())
            {
            StrategoIO.infoMsg("YOU LOST NUB!!!", "Results");
            winner = true;
            return new StrategoEvent(from.getMirroredCoordinate().hashCode(), 
                to.getMirroredCoordinate().hashCode());
            }
        return new StrategoEvent(from.getMirroredCoordinate().hashCode(), 
                to.getMirroredCoordinate().hashCode());
        }
        
    private boolean inCoordinateList(ArrayList<Coordinate> c, Coordinate cd)
        {
        for (Coordinate cdd : c)
            if (cdd.equals(cd))
                return true;
        return false;
        }
    
    private Coordinate moveSoldier(StrategoSoldier ss) throws InterruptedException
        {
        System.out.println("Entering Move Soldier");
        ArrayList<Coordinate> whereToGo = ss.getMoveCoordinates(
                netBuddy.getSoldiers(), local.getSoldiers());
        int x = 1;
        for (Coordinate wtg : whereToGo)
            System.out.println(x++ + " " + wtg);
        System.out.println("here : 5");
        Coordinate toWhere = null;
        while (toWhere == null)
            {
            while(!lib.clicked())
                Thread.sleep(100);
            Coordinate temp = lib.getLastCoordinate();
            //System.out.println(temp); Dont think this is needed anymore
            if (temp != null && inCoordinateList(whereToGo, temp))
                toWhere = temp;
            }
        System.out.println("here : 6");
        //We dont seem to be getting null pointer here...
        ss.setColor(Color.CYAN);//this line 
        //ss.draw(true);// and this line are tests seems this one is overkill
        StrategoSoldier soldierThere = getSoldierAt(toWhere);
        if (!isEmpty(toWhere, false) && netBuddy.isMine(soldierThere))
            ss.soldierConflict(soldierThere, toWhere,false);
        else
            {
            ss.moveLoc(toWhere, true);
            System.out.println("here : 6.5");
            }
        System.out.println("here : 7");
        redrawLines();
        return toWhere;
        }
    
    /**
     * Checks to see if the player has won the game or lost it.
     * 
     * @return
     *          True if you have your flag and your opponent doesn't. 
     *          False if you don't have your flag and your opponent has 
     *          his.
     */
    private boolean checkVictory()
        {
        int yourFlagCount = 0, hisFlagCount = 0;
        for(StrategoSoldier sold : local.getSoldiers())
            if(sold.getType() == Type.FLAG)
                yourFlagCount++;
        for(StrategoSoldier sold : netBuddy.getSoldiers())
            if(sold.getType() == Type.FLAG)
                hisFlagCount++;
        return (yourFlagCount > 0) && (hisFlagCount == 0);
        }
    
    /**
     * Tests if a <code>StrategoSoldier</code> can move from its location
     * 
     * @param ss 
     *      <code>StrategoSoldier</code> to test if it can move
     * @return 
     *      returns true 
     */    
    private boolean canMove(StrategoSoldier ss)
        {
        if (ss.getType() != Type.SOLDIER)
            {
            System.out.println("It is not a soldier");
            return false;
            }
        ArrayList<Coordinate> ac = null;
        try
            {
            ac = ss.getMoveCoordinates(netBuddy.getSoldiers(), local.getSoldiers());
            }
        catch(Exception ex)
            {
            System.err.println("It was actually here");
            ex.printStackTrace();
            }
        int coors = 0;
        if(ac != null)
            for (int i = 0; i < ac.size(); i++)
                {
                Coordinate coor = ac.get(i);
                StrategoSoldier s = getSoldierAt(coor.hashCode());
                if (s != null && !local.isMine(s))
                    coors++;
                else if(s == null)
                    coors++;
                }
        return coors > 0;
        }
    
    /**
     * 
     * @return a piece that is able to move
     */    
    private StrategoSoldier chooseMovePieces()
        {
        StrategoSoldier clicked = null;
        StrategoIO.infoMsg(CHOOSE_PIECE, MOVE_WHERE);
        while (clicked == null)
            {
            while(!lib.clicked())
                try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
                Logger.getLogger(Board.class.getName()).log(Level.SEVERE, null, ex);
            }
            Coordinate cdd = lib.getLastCoordinate();
                System.out.println("PRINTING\t" + cdd);
            if (cdd != null)
                {
                try
                    {
                    Coordinate cd = new Coordinate(cdd);
                    System.out.println(cd);
                    StrategoSoldier temp = getSoldierAt(cd.hashCode());
                    StrategoSoldier temp1 = getSoldierAt(cd.row(), cd.col());
                    System.out.println(temp);
                    System.out.println("THE SOLDIER FROM MAP IS\n" + temp1);
                    System.out.println("Is mine turned out to be " + 
                        local.isMine(temp));
                    System.out.println("Can move turned out to be " + 
                            canMove(temp));
                    if (canMove(temp) && local.isMine(temp))
                        {
                        System.out.println("Entered conditional of chooseMovePieces");
                        clicked = getSoldierAt(cd.hashCode());
                        clicked.setColor(Color.GREEN);//this line
                        clicked.draw(true);// and this like are tests
                        System.out.println("here : 2");
                        }
                    }
                catch(NullPointerException ex)
                    {
                    System.err.println("Click again for soldier");
                    StrategoIO.errorMsg("Click again for soldier");
                    System.err.println(ex);
                    ex.printStackTrace();
                    }                
                }
            }
        System.out.println("Checkpoint 1" + "Returning\n" + clicked);
        return clicked;
        }
    
     private boolean checkLoss() 
        {
        int yourFlagCount = 0, hisFlagCount = 0;
        for(StrategoSoldier sold : local.getSoldiers())
            if(sold.getType() == Type.FLAG)
                yourFlagCount++;
        for(StrategoSoldier sold : netBuddy.getSoldiers())
            if(sold.getType() == Type.FLAG)
                hisFlagCount++;
        return (yourFlagCount == 0) && (hisFlagCount > 0);
        }
    
        /**
         * Copys the <code>StrategoEvent</code> that occured from the 
         * <code>StrategoNetworkCom</code> 
         * 
         * @param se
         *      The <code>StrategoEvent</code> to be copied to this <code>Board</code>
         * @return
         *      True if everything copies correctly else false.
         */
    public boolean copyEvent(StrategoEvent se)
        {
        if (se.getOrigin() == null)
            return true;
        StrategoSoldier ss = getSoldierAt(se.getOrigin());
        if (ss == null)
            {
            StrategoIO.errorMsg("Critical Error: ss is null");
            StrategoIO.errorMsg("Closing Program");
            System.exit(-1);
            }
        Coordinate to = se.getTo();
        if (!isEmpty(to, false))
            {
            StrategoSoldier mine = getSoldierAt(to);
            if (!local.isMine(mine))
                {
                StrategoIO.errorMsg("Critical Error: soldier isn't mine");
                StrategoIO.errorMsg("Closing Program");
                System.exit(-1);
                }
            ss.soldierConflict(mine, to, true);
            }
        else            
            ss.moveLoc(to, false);            
        return true;// returns true if everything works fine as said before
        }
    
    /**
     * Gets a soldier from the specified <code>Coordinate</code>
     * 
     * @param cd
     *      The <code>Coordinate</code> to get the soldier from
     * @return
     *      The <code>StrategoSoldier</code> if there was one at the
     * <code>Coordinate</code> or null if there was no soldier there.
     * @throws java.lang.NullPointerException
     *      If the input <code>Coordinate</code> is null
     */
    public StrategoSoldier getSoldierAt(Coordinate cd) throws NullPointerException 
        {
        if(cd == null)
            throw new NullPointerException("Not a valid coordinate");
        for (StrategoSoldier ss : soldiers)
            if (ss.getCoordinate().equals(cd))
                return ss;        
        return null;
        }
    
    /**
     * Gets a soldier from the specified board location
     * 
     * @param hashNum
     *      The number of the board location from which to get the soldier from
     * @return
     *      The soldier if there is one or null if there is not one there
     */
    public StrategoSoldier getSoldierAt(int hashNum) 
        {
        for(StrategoSoldier ss : soldiers)
            if(ss.getCoordinate().hashCode() == hashNum)
                return ss;
        return null;
        }
    
    /**
     * Checks if a coordinate is occupied or not.
     * 
     * @param cd
     *      The coordinate to be checked if it is empty or not.
     * @param checkLakes
     *      If true checks lakes and soldier. If false checks only soldiers.
     * @return
     *      True if the location is not occupied by a lake or a soldier otherwise
     *      false.
     * @deprecated This method should be replaced for the alternate isEmpty in all
     * occurances as this one is not as usefull as the alternative.
     * @see gfs.Board#isEmpty(gfs.Coordinate, int)
     * @since NetStrat2.1
     */
    @Deprecated
    public boolean isEmpty(Coordinate cd, boolean checkLakes)
        {
        if(checkLakes)
            for (StrategoLake lake : lakes)
                if (cd.equals(lake.getCoordinate()))
                    return false;
        for (StrategoSoldier sold: soldiers)
            if (cd.equals(sold.getCoordinate()))
                return false;
        return true;
        }
    
    /**
     * Checks if a specified <code>Coordinate</code> is empty of either 
     * <code>StragtegoLake</code>s, <code>StragtegoSoldier</code>s, or both
     * @param cd
     *      The <code>Coordinate</code> to check if it is empty
     * @param checkWhich
     *      The integer value that determines what it will look for in the 
     * designated <code>Coordinate</code>. Looks for <code>StragtegoSoldier</code>s
     * (1), Looks for <code>StragtegoLake</code>s (2), or both (3)
     * @return
     *      True if there is not an object of the specified class in the 
     * designated <code>Coordinate</code>
     */
    public boolean isEmpty(Coordinate cd, int checkWhich)
        {                
        boolean lagos = true, solds = true;
        if(checkWhich == 1)
            for (StrategoSoldier sold : soldiers)
                if (cd.equals(sold.getCoordinate()))
                    solds = false;
        if(checkWhich == 2)
            for (StrategoLake lake : lakes)
                if (cd.equals(lake.getCoordinate()))
                    lagos = false;
        if(checkWhich == 3)
            return solds && lagos;
        else if(checkWhich == 2)
            return lagos;
            else
                return solds;
        }
    
    /**
     * Tests if a coordinate is within the range of rows from 6 to 9.
     * 
     * @param c
     *      The coordinate to be check the range of.
     * @return
     *      True if the coordinate is within rows 6 to 9 else false.
     */
    private boolean inRange(Coordinate c)
        {
        return (c.row() >= 6 && c.row() <= 9);
        }
    
    /**
     * Makes the local player set his pieces.
     * 
     * @return
     *      True if everything works properly else you better start crying. No
     *      really if it returns false go break your computer because its definitely
     *      not our code.
     */
    private boolean setPieces()
        {
        int val = 0;
        val = StrategoIO.confirmMsg("Do you want your pieces set randomly"
                , "Set Pieces");
        if(val == StrategoIO.CLOSED_OPTION || val == StrategoIO.OPT_YES)
            {
            int x = 0;
            val = StrategoIO.confirmMsg("Set them forward?", "Set pieces");
            if(val == StrategoIO.CLOSED_OPTION || val == StrategoIO.OPT_YES)
                {
                x = 60;
                for(StrategoSoldier ss : local.getSoldiers())
                    {
                    ss.setCoordinate(Coordinate.getCoorFromHash(x++));
                    ss.draw(true);
                    }
                }
            else
                {
                x = 99;
                for(StrategoSoldier ss : local.getSoldiers())
                    {
                    ss.setCoordinate(Coordinate.getCoorFromHash(x--));
                    ss.draw(true);
                    }
                }
            redrawLines();
            }
        else
            {
            StrategoIO.infoMsg("Please prepare to set all your pieces.", SET_PLAYER);
            for (StrategoSoldier ss: local.getSoldiers())
                {
                StrategoIO.infoMsg(SET_THIS + ss.getName(), SET_PLAYER);
                while (ss.getCoordinate() == null)
                    {
                    while(!lib.clicked())
                    try {
                    Thread.sleep(100);
                    } catch (InterruptedException ex) {}
                    Coordinate cd = lib.getLastCoordinate();
                    if (cd != null && local.isEmpty(cd) && inRange(cd))
                        {
                        ss.setCoordinate(cd);
                        ss.draw(true);
                        }
                    }
                redrawLines();
                }
            }
        return true;
        }
    
    //Data Members
    final public static String SET_PLAYER = "Set Player";
    final public static String SET_THIS = "Please pick a Coordinate to set your ";
    final public static String CHOOSE_PIECE = "Please Choose a piece to move.";
    final public static String CHOOSE_TITLE = "Move Piece";
    final public static String MOVE_WHERE = "Click the Coordinate where you" +
            " want to move";
    private ArrayList<ArrayList<Coordinate>> rows;
    private ArrayList<StrategoSoldier> soldiers;
    private Object[][] map;//this is a map of board
    //guys will go
    private ArrayList<StrategoLake> lakes;
    private StrategoPlayer local; // this player
    private StrategoPlayer netBuddy; // net player
    private StrategoGraphicLib lib;
    public boolean winner;
    public boolean turn;// is for the main loop so turn will go back
    public static final int Y_MIN = -300;
    public static final int Y_MAX = 300;
    public static final int X_MIN = -300;
    public static final int X_MAX = 300;
    public static final int MAX_SIDES = 10;
    public static final int FINAL_RAD = 30;
    public static final boolean IS_LOCAL_PLAYER = true;
    public static final boolean IS_NETWORK_PLAYER = false;
    }
